Skip to content

fix(debuginfo): Add some dwarf debuginfo recursion limits#1015

Open
klochek wants to merge 7 commits into
masterfrom
christopherklochek/dwarf_recursion_limits
Open

fix(debuginfo): Add some dwarf debuginfo recursion limits#1015
klochek wants to merge 7 commits into
masterfrom
christopherklochek/dwarf_recursion_limits

Conversation

@klochek

@klochek klochek commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Ref: INGEST-967

@klochek
klochek requested a review from a team as a code owner July 9, 2026 13:01
@linear-code

linear-code Bot commented Jul 9, 2026

Copy link
Copy Markdown

INGEST-967

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Fixes

- Add some dwarf debuginfo recursion limits ([#1015](https://github.com/getsentry/symbolic/pull/1015))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against 1a82117

Comment thread symbolic-debuginfo/src/dwarf.rs
Comment thread symbolic-debuginfo/src/dwarf.rs Outdated
Comment on lines +564 to +565
/// The maximum depth to recurse to when parsing inlined functions.
const MAX_PARSE_INLINEE_DEPTH: u32 = 256;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this you could piggyback on the max_inline_depth option introduced in #1014.

@klochek
klochek force-pushed the christopherklochek/dwarf_recursion_limits branch from 18c09ee to 9921fec Compare July 10, 2026 18:52
Comment thread symbolic-debuginfo/src/dwarf.rs
Comment thread symbolic-debuginfo/src/dwarf.rs Outdated

@loewenheim loewenheim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM apart from the function removals. I think it's not uncommon in Rust for functions/methods to be defined both on a trait and on a type directly; this lets one use the function even if the trait isn't in scope. I would leave the existing functions in place if there isn't a strong reason to remove them.

matches!(MachArchive::is_fat(data), Some(false))
}

/// Tries to parse a MachO from the given slice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave this function in place. The removal is breaking for no real reason, IMO.

Comment thread symbolic-debuginfo/src/wasm/parser.rs Outdated
Self::test(data)
}

fn parse_with_opts(data: &'d [u8], popts: ParseObjectOptions) -> Result<Self, Self::Error> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for consistency:

Suggested change
fn parse_with_opts(data: &'d [u8], popts: ParseObjectOptions) -> Result<Self, Self::Error> {
fn parse_with_opts(data: &'d [u8], opts: ParseObjectOptions) -> Result<Self, Self::Error> {

}

/// Tries to parse a PE object from the given slice.
pub fn parse(data: &'data [u8]) -> Result<Self, PeError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for macho.

@klochek
klochek requested a review from loewenheim July 24, 2026 01:47
@klochek
klochek force-pushed the christopherklochek/dwarf_recursion_limits branch from 4c8c487 to 4b5e224 Compare July 24, 2026 15:19

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4b5e224. Configure here.

Comment thread symbolic-debuginfo/src/wasm/parser.rs
let mut entries = self.inner.unit.entries_raw(None)?;
let mut output = FunctionsOutput::with_seen_ranges(seen_ranges);
self.parse_functions(-1, &mut entries, &mut output)?;
self.parse_functions(-1, max_inline_depth, &mut entries, &mut output)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse_function / parse_function_children lack depth limit for nested DW_TAG_subprogram

While the PR caps inlined-subroutine recursion with max_inline_depth, nested DW_TAG_subprogram entries are still traversed via mutual recursion between parse_function and parse_function_children with no depth bound. A crafted DWARF file with a chain of deeply nested subprograms causes an unbounded stack growth and an uncatchable stack-overflow abort.

Evidence
  • DwarfUnit::functions() at line 1359 initiates parsing by calling self.parse_functions(-1, max_inline_depth, &mut entries, &mut output) — the added max_inline_depth is only checked later on inlinees, not on subprograms.
  • parse_functions (line 839) delegates DW_TAG_subprogram entries to parse_function (line 855), passing remaining_inline_depth unchanged.
  • parse_function (line 880) reads attributes then calls parse_function_children (line 979).
  • parse_function_children (line 1019) recursively calls parse_function for nested DW_TAG_subprogram children at greater next_depth (line 1043) with no limit on nesting depth.
  • A malicious DWARF with a chain of nested subprograms creates an unbounded call stack (parse_functionparse_function_childrenparse_function → …), while the sibling inlinee path was capped in parse_inlinee (line 1099).
Also found at 5 additional locations
  • symbolic-debuginfo/src/dwarf.rs:1760
  • symbolic-debuginfo/src/pe.rs:260
  • symbolic-debuginfo/src/dwarf.rs:586
  • symbolic-debuginfo/src/dwarf.rs:1893
  • symbolic-debuginfo/src/elf.rs:568

Identified by Warden · wrdn-dos-review · EVE-PMU

Comment thread symbolic-debuginfo/src/elf.rs
Comment thread symbolic-debuginfo/src/elf.rs
Comment thread symbolic-debuginfo/src/pe.rs
Comment on lines 174 to +175
max_decompressed_section_size: opts.max_decompressed_section_size,
max_inline_depth: opts.max_inline_depth,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ELF decompression size guard defaults to unlimited, allowing OOM abort on tiny input

max_decompressed_section_size defaults to None, which disables the size check and lets a tiny crafted ELF file declare a multi-GB compressed section, causing Vec::with_capacity(size) to abort the allocator.

Evidence
  • ParseObjectOptions::default() sets max_decompressed_section_size: Default::default() which is None (object.rs:152).
  • In decompress_section, size is read from the attacker-controlled ELF compression header (elf.rs:610–634).
  • The check size > self.max_decompressed_section_size.unwrap_or(usize::MAX) (elf.rs:637) becomes size > usize::MAX when the option is None, so it never triggers.
  • Vec::with_capacity(size) (elf.rs:643) is then called with the attacker-declared size, up to usize::MAX, causing an allocator abort on a tiny crafted input.
  • The same ElfObject struct initialized on line 174 carries this ineffective limit into decompress_section.
Also found at 2 additional locations
  • symbolic-debuginfo/src/elf.rs:359-359
  • symbolic-debuginfo/src/object.rs:148-160

Identified by Warden · wrdn-dos-review · NRR-XKG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants